Skip to content

#22411 - Enforce token write ability when running scripts via the REST API#22412

Open
arthanson wants to merge 3 commits into
featurefrom
22411-script
Open

#22411 - Enforce token write ability when running scripts via the REST API#22412
arthanson wants to merge 3 commits into
featurefrom
22411-script

Conversation

@arthanson

@arthanson arthanson commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Closes: #22411

The script execution endpoint (POST /api/extras/scripts/{id}/) did not honor the calling token's write_enabled flag. ScriptViewSet overrides permission_classes with [IsAuthenticatedOrLoginNotRequired], which removes TokenPermissions (the only permission class that consults request.auth.write_enabled) from the chain. As a result, a read-only token (write_enabled=False) whose user holds the run_script permission could still trigger script execution, bypassing the read-only restriction that operators expect such tokens to enforce.

Changes

  • Added an explicit token write-ability check in ScriptViewSet.post(): when the request is token-authenticated (request.auth is a Token) and the token has write_enabled=False, the request is rejected with 403 Forbidden. The existing object-level run_script permission check is retained.
  • The check is intentionally scoped to token authentication. This endpoint also supports session authentication via IsAuthenticatedOrLoginNotRequired, so session-authenticated requests (where request.auth is not a Token) are unaffected and continue to work as before.
  • Added ScriptTestCase.test_run_token_write_enabled: a write_enabled=False token (with the run_script permission) receives 403 Forbidden, and 200 OK once the token's write ability is enabled.
  • Added ScriptTestCase.test_run_session_auth: a session-authenticated request (no token) is still permitted to run scripts, guarding against a regression in the session-auth path.

Notes

The other REST API viewsets that override permission_classes with IsAuthenticatedOrLoginNotRequired (StatusView, ObjectTypeViewSet, ConnectedDeviceViewSet) expose only read-only (GET) methods, so the missing write check has no effect there — ScriptViewSet was the sole affected endpoint.

@netbox-community netbox-community deleted a comment from github-actions Bot Jun 8, 2026
@arthanson

Copy link
Copy Markdown
Collaborator Author

@claude /review Only report on issues of potential concern; we don't need commentary on anything that doesn't require further consideration.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @arthanson's task in 2m 56s —— View job


Code Review

  • Gather context
  • Read changed files
  • Post review

Two items worth noting:

1. Write-check placed after script lookup (minor info leak)

In views.py:340, the write_enabled check fires only after _get_script(pk) succeeds. That means a read-only token whose user holds run_script permission will receive 404 for non-existent scripts and 403 for existing ones — leaking existence. Reversing the order (check write_enabled before fetching the script) would return 403 uniformly, before any object-level query runs. This is minor since the user already has run_script permission, but worth considering.

# Current order:
script = self._get_script(pk)         # 404 if not found
if isinstance(request.auth, Token) and not request.auth.write_enabled:  # 403 after
    raise PermissionDenied(...)

# Safer order:
if isinstance(request.auth, Token) and not request.auth.write_enabled:  # 403 first
    raise PermissionDenied(...)
script = self._get_script(pk)

Fix this →

2. Inconsistency with ConfigTemplateViewSet.render() security model

ConfigTemplateViewSet.render() (line 247) gates write checks via TokenWritePermission in get_permissions(), which also blocks session auth entirely. ScriptViewSet.post() uses a manual isinstance check to allow session auth through. The asymmetry isn't a bug (the PR description explains the intent), but it means two different patterns for "POST action that bypasses standard CRUD permissions" now exist in the same file. If ConfigTemplateViewSet.render() ever needs to accept session auth too, the pattern will need to be revisited.


No other concerns. The fix is correct, the import path matches authentication.py's usage, and the tests cover both the blocked and allowed paths as well as the session-auth regression guard.

@arthanson

Copy link
Copy Markdown
Collaborator Author

Per claude review, addressed 1, 2 is a direct consequence of these changes - collapsing them back to one pattern would reintroduce the bug.

@arthanson arthanson marked this pull request as ready for review June 9, 2026 01:03
@arthanson arthanson requested a review from jeremystretch June 9, 2026 01:03

@jeremystretch jeremystretch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree generally with the assertion that executing a custom script should require write permissions, this would be a breaking change. IMO we should target it for NetBox v4.7. (Consider a scenario where a read-only API token is used to execute a custom script that performs only read operations, e.g. to generate some report.)

@arthanson arthanson changed the base branch from main to feature June 10, 2026 20:28
@arthanson

Copy link
Copy Markdown
Collaborator Author

rebased to feature branch

@arthanson arthanson requested a review from jeremystretch June 10, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enforce token write ability on the REST API script execution endpoint

2 participants